package org.testmyapp.engine;

import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.activiti.engine.FormService;
import org.activiti.engine.HistoryService;
import org.activiti.engine.IdentityService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.form.FormProperty;
import org.activiti.engine.form.TaskFormData;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.parser.BpmnParse;
import org.activiti.engine.impl.bpmn.parser.BpmnParser;
import org.activiti.engine.impl.cfg.DefaultBpmnParseFactory;
import org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.el.ExpressionManager;
import org.activiti.engine.impl.form.FormTypes;
import org.activiti.engine.impl.persistence.entity.DeploymentEntity;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.task.Task;
import org.testmyapp.persistence.BusinessObjectMarker;

@Named
@SessionScoped
public class ActivitiEngineServices implements Serializable, IEngineServices {

	/**
	 * 
	 */

	public static String seperator = ",";

	private static final long serialVersionUID = 1138045446775463432L;

	@Inject
	private IdentityService identityService;

	@Inject
	private RuntimeService runtimeService;

	@Inject
	private TaskService taskService;

	@Inject
	private HistoryService historyService;

	@Inject
	private FormService formService;

	@Inject
	private RepositoryService repositoryService;

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#getAvailableProcess()
	 */
	public List<ProcessDefinition> getAvailableProcess() {
		List<ProcessDefinition> list = repositoryService
				.createProcessDefinitionQuery().active().latestVersion().list();
		return list;
	}

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#startProcessForUserByKey(java.lang.String, java.lang.String)
	 */
	public boolean startProcessForUserByKey(String userId, String processKey) {
		try {
			Map<String, Object> variables = new HashMap<String, Object>();
			variables.put("user", userId);
			// ProcessDefinition procdef = ep.getEngine().getRepositoryService()
			// .createProcessDefinitionQuery().deploymentId("3601")
			// .singleResult();
			// runtimeService.startProcessInstanceById(procdef, variables);
			runtimeService.startProcessInstanceByKey(processKey, variables);
		} catch (Exception e) {
			return false;
		}
		return true;
	}

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#getTasksForCurrentUser(java.lang.String)
	 */
	public List<Task> getTasksForCurrentUser(String userId) {
		List<Task> tasksForUser;
		tasksForUser = taskService.createTaskQuery().taskAssignee(userId)
				.list();
		return tasksForUser;
	}

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#getTaskById(java.lang.String)
	 */
	public Task getTaskById(String taskId) {
		Task taskById = taskService.createTaskQuery().taskId(taskId)
				.singleResult();
		return taskById;
	}

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#getFormPropertiesForTask(org.activiti.engine.task.Task)
	 */
	public List<FormProperty> getFormPropertiesForTask(Task taskById) {
		TaskFormData taskFormData = formService.getTaskFormData(taskById
				.getId());
		List<FormProperty> formProperties = taskFormData.getFormProperties();
		return formProperties;
	}

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#getProcessDefinitionKeyForTaskDefKey(java.lang.String)
	 */
	public String getProcessDefinitionKeyForTaskDefKey(
			String processDefinitionId) {
		ProcessDefinition processDef = repositoryService
				.createProcessDefinitionQuery()
				.processDefinitionId(processDefinitionId).singleResult();
		String processDefKey = processDef.getKey();
		return processDefKey;
	}

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#getValueOfVariable(org.activiti.engine.form.FormProperty, java.lang.String)
	 */
	public BusinessObjectMarker getValueOfVariable(FormProperty fp,
			String processInstanceId) {
		String varName = fp.getId();
		Execution execution = runtimeService.createExecutionQuery()
				.processInstanceId(processInstanceId).singleResult();
		BusinessObjectMarker variable = (BusinessObjectMarker) runtimeService
				.getVariable(execution.getId(), varName);
		return variable;
	}

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#getVariable(java.lang.String, java.lang.String)
	 */
	public Object getVariable(String var, String processInstanceId) {
		List<Execution> executions = runtimeService.createExecutionQuery()
				.processInstanceId(processInstanceId).list();
		Object variable = null;
		for (Execution e : executions) {
			if (runtimeService.getVariable(e.getId(), var) != null) {
				variable = runtimeService.getVariable(e.getId(), var);
			}
		}
		// Logging
		System.out.println("Retrieved: " + var + " from process "
				+ processInstanceId + ", value: " + variable);
		return variable;
	}

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#addBusinessRelevantProcessVarToProcess(java.lang.String, java.lang.String)
	 */
	public void addBusinessRelevantProcessVarToProcess(String pid, String value) {
		Execution exe = runtimeService.createExecutionQuery()
				.processInstanceId(pid).singleResult();
		String persiList;
		if (runtimeService.getVariable(exe.getId(), "persistenceList") == null) {
			persiList = value;
		} else {
			persiList = runtimeService.getVariable(exe.getId(),
					"persistenceList") + seperator + value;
		}
		// technical relevance...
		runtimeService.setVariable(exe.getId(), "persistenceList", persiList);
	}

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#setVariable(java.lang.String, java.lang.String, java.lang.Object)
	 */
	public void setVariable(String pid, String varName, Object object) {
		List<Execution> exe = runtimeService.createExecutionQuery()
				.processInstanceId(pid).list();
		if (!(object instanceof Serializable)) {
			throw new RuntimeException("cant save process var");
		}
		System.err.println("process var created: " + varName + " obj: "
				+ object);
		for(Execution e : exe) {
			runtimeService.setVariable(e.getId(), varName, object);	
		}
		
	}

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#completeTask(org.activiti.engine.task.Task)
	 */
	public void completeTask(String taskId) {
		taskService.complete(taskId);
	}

	/* (non-Javadoc)
	 * @see org.testmyapp.engine.IEngineServices#getIdentityService()
	 */
	public IdentityService getIdentityService() {
		return identityService;
	}
	
	public List<String> completeUserTaskWithSucessorDetermination(Task task) {
		List<String> result = new ArrayList<String>();
		List<String> result2 = new ArrayList<String>();
		if (Context.getProcessEngineConfiguration() == null) {
			StandaloneProcessEngineConfiguration processEngineConfiguration = new StandaloneProcessEngineConfiguration();
			processEngineConfiguration.setFormTypes(new FormTypes());
			processEngineConfiguration.setExpressionManager(new ExpressionManager());
			Context.setProcessEngineConfiguration(processEngineConfiguration);
		}
		if (Context.getProcessEngineConfiguration().getFormTypes() == null) {
			Context.getProcessEngineConfiguration().setFormTypes(new FormTypes());
		}
        List<Task> tasksBefore = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
        InputStream processModel = repositoryService.getProcessModel(task.getProcessDefinitionId());
        tasksBefore.remove(task);
		
		BpmnParse createBpmnParse = new DefaultBpmnParseFactory().createBpmnParse(new BpmnParser(new ExpressionManager(), new DefaultBpmnParseFactory()));
		createBpmnParse.sourceInputStream(processModel);
		DeploymentEntity deployment2 = new DeploymentEntity();
		// We have to fake a deployment for this
		deployment2.setId("-1");
		createBpmnParse.deployment(deployment2);
		createBpmnParse.execute();
		createBpmnParse.parseProcessDefinitions();
		List<ProcessDefinitionEntity> processDefinitions = createBpmnParse.getProcessDefinitions();
		
		ActivityImpl taskActivity = null;
		for (ActivityImpl activity : processDefinitions.get(0).getActivities()) {
			if (activity.getId().equals(task.getTaskDefinitionKey())) {
				taskActivity = activity;
			}
		}
		if (taskActivity == null) throw new RuntimeException();
		result = traverseGraphAndReturnTasks(taskActivity);
		completeTask(task.getId());

        List<Task> tasksAfter = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
        tasksAfter.removeAll(tasksBefore);
        for (Task taskCheck : tasksAfter) {
        	boolean notInlist = true;
        	for (String s : result) {
        		if (s.equals(taskCheck.getTaskDefinitionKey())) {
        			notInlist = false;
        			result2.add(taskCheck.getId());
        			break;
        		}
        	}
        	if(notInlist) result.remove(taskCheck.getId());
        }
		return result2;
	}

	private List<String> traverseGraphAndReturnTasks(ActivityImpl taskActivity) {
		List<String> result = new ArrayList<String>();
		for (PvmTransition transition : taskActivity.getOutgoingTransitions()) {
			ActivityImpl destination = (ActivityImpl) transition.getDestination();
			if (destination.getActivityBehavior() instanceof UserTaskActivityBehavior) {
				result.add(destination.getId());
			}
			else {
				result.addAll(traverseGraphAndReturnTasks(destination));
			}
		}
		return result;
	}
	
	public Map<String, Object> getProcVariables(String procIns) {
		List<Execution> list = runtimeService.createExecutionQuery().processInstanceId(procIns).list();
		String id = list.get(0).getId();
		Map<String, Object> variables = runtimeService.getVariables(id);
		return variables;
	}

}
